package com.me.study.javaCore.ssl;

import com.me.common.util.StringUtils;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;

/**
 * Java SSL 认证：客户端
 * 可以通过 `System.setProperty("javax.net.debug", "all");` 开启网络调试
 *
 * @author ME
 * @date 2021/8/9
 */
public class SSLClientTwoWay {

    private static final String DEFAULT_HOST = "127.0.0.1";
    private static final int DEFAULT_PORT = 7777;
    private static final String CLIENT_KEY_STORE_PASSWORD = "123456";
    private static final String CLIENT_TRUST_KEY_STORE_PASSWORD = "123456";
    private SSLSocketFactory socketFactory;

    /**
     * 启动客户端程序
     *
     * @param args
     */
    public static void main(String[] args) {
        System.setProperty("javax.net.debug", "ssl,handshake");
        System.setProperty("javax.net.debug", "all");

        SSLClientTwoWay client = new SSLClientTwoWay();
        client.init();
        client.process();
    }

    /**
     * 通过ssl socket与服务端进行连接,并且发送一个消息
     */
    public void process() {
        try (Socket client = socketFactory.createSocket(DEFAULT_HOST, DEFAULT_PORT)) {
            DataOutputStream out = new DataOutputStream(new BufferedOutputStream(client.getOutputStream()));
            out.writeUTF("Client Message：two way...");
            out.writeUTF(SSLServer.END);
            out.flush();

            DataInputStream input = new DataInputStream(new BufferedInputStream(client.getInputStream()));
            // 循环读取
            StringBuilder msg = new StringBuilder();
            String temp;
            int index;
            while (StringUtils.isNotEmpty(temp = input.readUTF())) {
                // 遇到 eof 时就结束接收
                if ((index = temp.indexOf(SSLServer.EOF)) != -1) {
                    msg.append(temp, 0, index);
                    break;
                }
                msg.append(temp);
            }
            System.out.println("【server】 " + msg);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * ssl连接的重点：
     * 初始化 SSLSocket
     * 导入客户端私钥 KeyStore，导入客户端受信任的 KeyStore（服务端的证书）
     */
    public void init() {
        try {
            // KeyStore，确保客户端在 SSL 握手期间提供其证书给服务器，以便服务器验证客户端的身份
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(Files.newInputStream(Paths.get("testJDK8/src/main/java/com/me/study/javaCore/ssl/kserver.jks")),
                    CLIENT_KEY_STORE_PASSWORD.toCharArray());
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());

            // TrustStore，确保客户端在 SSL 握手期间验证服务器的证书。如果服务器没有提供有效的证书，握手会失败。
            KeyStore tks = KeyStore.getInstance("JKS");
            tks.load(Files.newInputStream(Paths.get("testJDK8/src/main/java/com/me/study/javaCore/ssl/tclient.jks")),
                    CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(tks);

            SSLContext ctx = SSLContext.getInstance("SSL");
            ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
            // 打印这个SSLContext实例使用的协议
            System.out.println("TwoWay 安全套接字使用的协议: " + ctx.getProtocol() + ", algorithm: " + tmf.getAlgorithm());

            socketFactory = ctx.getSocketFactory();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

}
